/*
 * NetRayt
 * Copyright (c) 2007-2008 Maciej Nabozny.
 *
 * NetRayt is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * NetRayt is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NetRayt; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifndef _CONN_THREAD_H
#define _CONN_THREAD_H

#include "conn.h"

/*******************************************/
/* Klasa klienta, obsługa osobnego procesu */
/*******************************************/
connThread::connThread(QTcpSocket *c) {
	conn = c;
	
	connect(conn, SIGNAL(readyRead()), this, SLOT(parseData()));
	randomName();
}

connThread::~connThread() {
	delete conn;
}

/*
 * Uruchom wątek
 */
void connThread::run() {
	qDebug("connThread::run: Uruchamiam nowy watek");
	//exec();
}

/*
 * Zwróć 8 losowych znaków
 */
QString connThread::randomName() {
	char start = 'a';
	char diff = 'z'-'a';
	QString r = "";
	for (int i = 0; i < 8; i++) {
		r += start + qrand()%diff;
	}
	return r;
}

/*
 * Zamień tag z argumentami na argList
 */
argList connThread::parseArgs(QString argStr) {
	argList dataArg;
	// regexp, który odczytuje wartości: argument="value" z żądania
	QRegExp rx("\\s([a-zA-Z0-9]+)=\\\"([a-zA-Z0-9\\.\\/\\-]+)\\\"");
	// pozycja ostatnio odczytanego łańcucha
	int pos = 0;
	
	// dopisz do argList kolejne wartości
	while ((pos = rx.indexIn(argStr, pos)) != -1) {
		dataArg.set(rx.cap(1), rx.cap(2));
		pos+=rx.matchedLength();
	}
	
	QRegExp rx2("(request|response)");
	rx2.indexIn(argStr);
	dataArg.set("request_type", rx2.cap(1));
	qDebug(dataArg.read("request_type").toAscii());
	return dataArg;
}

/*
 * Pobierz dodatkowe dane od klienta
 * informacje o danych zapisuje do &dataArg (referencja)
 * Dane pobierane są z *conn, przekazanego przez konstruktor
 */
QString connThread::fetchData(argList &dataArg) {
	// Odczytaj dane jeżeli są
	if (dataArg.read("datasize").size() != 0) {
		int datasize = dataArg.read("datasize").toInt();
		int filesize = 0;
		
		QString filename = TEMP_DIR + "/netRayt_" + randomName();
		QFile tmp(filename);
		if (!tmp.open(QFile::WriteOnly)) {
			qDebug("connThread::fetchData: Nie mozna zapisac odbieranego pliku");
			return "";
		}
		
		QDataStream out(&tmp);
		QDataStream in(conn);
		
		while (1) {
			char buff[1024*1024 + 1];
			
			conn->waitForReadyRead(1);
			int bytes = in.readRawData(buff, 1024 * 1024);
			out.writeRawData(buff, bytes);
			
			filesize += bytes;
			qDebug(QString("connThread::fetchData: Odebralem %1 z %2").arg(filesize).arg(datasize).toAscii());
			
			if (filesize >= datasize) {
				break;
			}
		}
		
		return filename;
		tmp.close();
	} else {
		// zwróć pusty string jeżeli nie ma dodatkowych danych
		return "";
	}
}

/*
 * Odkoduj z base64:
 */
QString connThread::decodeFile(QString filename) {
	// Otwórz plik źródłowy
	QFile fp(filename);
	if (!fp.open(QIODevice::ReadOnly)) {
		qDebug(filename.toAscii());
		qDebug("connThread::decodeFile: Nie mozna otworzyc pliku base64!");
		return "";
	}
	
	// otwórz plik docelowy
	QString filename2=TEMP_DIR + "/netRayt_" + randomName();
	QFile fp2(filename2);
	if (!fp2.open(QIODevice::WriteOnly)) {
		qDebug("connThread::decodeFile: Nie mozna utworzyc pliku base64!");
		return "";
	}
	
	QDataStream in(&fp);
	QDataStream out(&fp2);
	
	int bytes = 0;
	int dataSize = 0;
	int dataMaxSize = fp.size();
	
	char buff[512];
	QByteArray buff_dec;
	
	// Odkodowanie pliku, odczytuje po 345 znaki, ZAWSZE
	while (1) {
		bytes = fp.readLine(buff, 345);
		buff[345] = 0;
		dataSize += bytes;
		buff_dec = QByteArray::fromBase64(buff);
		
		out.writeRawData(buff_dec, buff_dec.size());
		
		if (dataSize >= dataMaxSize) {
			break;
		}
	}
	
	fp2.close();
	fp.close();
	//qDebug(filename2.toAscii());
	return filename2;
}

/*
 *  Zakoduj do base64
 */
QString connThread::encodeFile(QString filename) {
	// Otwórz pliki
	QFile source(filename);
	if (!source.open(QIODevice::ReadOnly)) {
		qDebug("connThread::encodeFile: Nie mozna otworzyc pliku do odczytu");
		return "";
	}
	
	QString filename2 = TEMP_DIR + "/netRayt_" + randomName();
	QFile dest(filename2);
	if (!dest.open(QIODevice::WriteOnly)) {
		qDebug("connThread::encodeFile: Nie mozna otworzyc pliku do zapisu");
		return "";
	}
	
	// Stwórz strumienie danych:
	
	QDataStream in(&source);
	QDataStream out(&dest);
	
	// Rozmiar pliku i ilosc odczytanych danych
	int filesize = source.size();
	qDebug(QString("connThread::encodeFile: Rozmiar pliku do zakodowania: %1").arg(filesize).toAscii());
	int datasize = 0;
	char buff[512];
	
	while (1) {
		int bytes = in.readRawData(buff, 256);
		
		QByteArray buff_dec, buff_enc;
		for (int i = 0; i < bytes; i++) {
			buff_dec.push_back(buff[i]);
		}
		
		buff_enc = buff_dec.toBase64();
		out.writeRawData(buff_enc, buff_enc.size());
		
		datasize += bytes;
		
		qDebug(QString("connThread::encodeFile: Ilosc odczytanych danych: %1. Rozmiar lini: %2").arg(datasize).arg(buff_enc.size()).toAscii());
		if (datasize >= filesize) {
			break;
		}
	}
	
	source.close();
	dest.close();
	return filename2;
}

/*
 * Przetwórz dane otrzymane przez połączenie
 * i wyemituj odpowiedni sygnał
 */
void connThread::parseData() {
	// stwórz buffor
	char *buff=new char[BUFF_SIZE];
	
	// odczytaj linie z tagiem
	conn->readLine(buff, BUFF_SIZE);
	QString qbuff = buff;
	
	// pobierz parametry z pobranego tagu
	argList dataArg = parseArgs(qbuff);
	
	// zapisz dodatkowe dane do pliku filename i odkoduj plik
	QString filename = fetchData(dataArg);
	if (filename != "") {
		filename = decodeFile(filename);
	}
	
	// wyemituj odpowiedni sygnał
	if (dataArg.read("request_type") == "request") {
		qDebug("connThread::parseData: Emituje request");
		emit(recRequest(dataArg, filename));
	} else if (dataArg.read("request_type") == "response") {
		qDebug("connThread::parseData: Emituje response");
		emit(recResponse(dataArg, filename));
	}
	
	delete buff;
}

/*
 * Wyślij odpowiedź
 */
void connThread::sendResponse(argList dataArg, QString filename) {
	// Zakodowanie pliku do base64
	QString fn;
	if (filename != "") {
		fn = encodeFile(filename);
		
		QFile fp(fn);
		dataArg.set("datasize", QString("%1").arg(fp.size()).toAscii());
	}
	
	// Wyslanie lini z argumentami argList
	QString response = "<response";
	response += dataArg.toTag();
	response += ">\n";
	
	qDebug(QString("connThread::sendResponse: Wysylam odpowiedz: %1").arg(response).toAscii());
	conn->write(response.toAscii(), response.size());
	
	// Przeslanie zakodowanego pliku, jezeli zostal podany
	if (filename != "") {
		// Otworz plik zrodlowy i przeksztalc na strumien
		QFile src(fn);
		if (!src.open(QFile::ReadOnly)) {
			qDebug("connThread::sendResponse: Nie mozna otworzyc pliku do wyslania");
		}
		QDataStream in(&src);
		
		int filesize = src.size();
		int datasize = 0;
		while (1) {
			char buff[1024*1024 + 1];
			int bytes = in.readRawData(buff, 1024*1024);
			
			// Wyslij pakiet danych
			conn->waitForBytesWritten();
			conn->write(buff, bytes);
			
			datasize += bytes;
			
			qDebug(QString("connThread::sendResponse: Wyslane %1 z %2 bajtow").arg(datasize).arg(filesize).toAscii());
			if (datasize >= filesize) {
				break;
			}
		}
		conn->waitForBytesWritten();
		conn->write("\n", 1);
		conn->waitForBytesWritten();
	}
}


/*
 * Wyślij żądanie
 */
void connThread::sendRequest(argList dataArg, QString filename) {
	// Zakodowanie pliku do base64
	QString fn;
	if (filename != "") {
		fn = encodeFile(filename);
		
		QFile fp(fn);
		dataArg.set("datasize", QString("%1").arg(fp.size()).toAscii());
	} else {
		qDebug("connThread::sendRequest: Brak pliku");
	}
	
	// Wyslanie lini z argumentami argList
	QString request = "<request";
	request += dataArg.toTag();
	request += ">\n";
	
	qDebug(QString("connThread::sendRequest: Wysylam zadanie: %1").arg(request).toAscii());
	conn->write(request.toAscii(), request.size());
	
	// Przeslanie zakodowanego pliku, jezeli zostal podany
	if (filename != "") {
		// Otworz plik zrodlowy i przeksztalc na strumien
		QFile src(fn);
		if (!src.open(QFile::ReadOnly)) {
			qDebug("connThread::sendRequest: Nie mozna otworzyc pliku do wyslania");
		}
		QDataStream in(&src);
		
		int filesize = src.size();
		int datasize = 0;
		while (1) {
			char buff[1024*1024 + 1];
			int bytes = in.readRawData(buff, 1024*1024);
			
			// Wyslij pakiet danych
			conn->waitForBytesWritten();
			conn->write(buff, bytes);
			
			datasize += bytes;
			
			qDebug(QString("connThread::sendRequest: Wyslane %1 z %2 bajtow").arg(datasize).arg(filesize).toAscii());
			if (datasize >= filesize) {
				break;
			}
		}
		conn->waitForBytesWritten();
		conn->write("\n", 1);
		conn->waitForBytesWritten();
	}
}

void connThread::test_encoding() {
	QString fn = encodeFile("/bin/bash");
	qDebug(fn.toAscii());
	QString dfn = decodeFile(fn);
	qDebug(dfn.toAscii());
}

#endif

